﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace K054539
{
    public class Decoder
    {
        protected CodecProperties Properties;

        public Decoder(CodecProperties newProperties)
        {
            Properties = newProperties;
        }

        public short[] Decode()
        {
			Properties.LoopEnabled = false;
            short[] result = null;
            byte[] flagQueue = new byte[16];
            MemoryStream outputMemory = new MemoryStream();

            // prebuffer
            Properties.Input.Read(flagQueue, 0, 16);

            while (true)
            {
                if (((flagQueue[12] & 0xF0) == 0x80) &&
                    flagQueue[13] == 0x88 &&
                    flagQueue[14] == 0x88 &&
                    flagQueue[15] == 0x88)
                {
                    // decode 4-bit DPCM
                    outputMemory.Write(flagQueue, 0, 12);
                    outputMemory.Flush();
                    outputMemory.Position = 0;
                    result = Decode4Bit(outputMemory);
                    break;
                }
                else if (flagQueue[8] == 0x80 &&
                    flagQueue[9] == 0x80 &&
                    flagQueue[10] == 0x80 &&
                    flagQueue[11] == 0x80 &&
                    flagQueue[12] == 0x80 &&
                    flagQueue[13] == 0x80 &&
                    flagQueue[14] == 0x80 &&
                    flagQueue[15] == 0x80)
                {
                    // decode 8-bit PCM
                    outputMemory.Write(flagQueue, 0, 8);
                    outputMemory.Flush();
                    outputMemory.Position = 0;
                    result = Decode8Bit(outputMemory);
                    break;
                }
                else if (flagQueue[0] == 0x00 &&
                    flagQueue[1] == 0x80 &&
                    flagQueue[2] == 0x00 &&
                    flagQueue[3] == 0x80 &&
                    flagQueue[4] == 0x00 &&
                    flagQueue[5] == 0x80 &&
                    flagQueue[6] == 0x00 &&
                    flagQueue[7] == 0x80 &&
                    flagQueue[8] == 0x00 &&
                    flagQueue[9] == 0x80 &&
                    flagQueue[10] == 0x00 &&
                    flagQueue[11] == 0x80 &&
                    flagQueue[12] == 0x00 &&
                    flagQueue[13] == 0x80 &&
                    flagQueue[14] == 0x00 &&
                    flagQueue[15] == 0x80)
                {
                    // decode 16-bit PCM
                    outputMemory.Flush();
                    outputMemory.Position = 0;
                    result = Decode16Bit(outputMemory);
                    break;
                }

                int inputByte = Properties.Input.ReadByte();
                if (inputByte == -1)
                {
                    // end of stream
                    break;
                }
                outputMemory.WriteByte(flagQueue[0]);

                for (int i = 0; i < 15; i++)
                {
                    flagQueue[i] = flagQueue[i + 1];
                }
                flagQueue[15] = (byte)inputByte;
            }

            outputMemory.Dispose();
            return result;
        }

        private short[] Decode4Bit(Stream source)
        {
            List<short> result = new List<short>();
            int count = (int)source.Length;
            int[] sample = new int[2];
            int delta = 0;
            byte sampleByte;
            int outputSample;
            int[] table = Properties.DPCMTable;
			int loopStart = 0;
			int loopLength = 0;
			bool loopEnabled = false;

            for (int i = 0; i < count; i++)
            {
                sampleByte = (byte)source.ReadByte();
                if (!Properties.SwapNybble)
                {
                    sample[0] = table[sampleByte & 0x0F];
                    sample[1] = table[(sampleByte & 0xF0) >> 4];
                }
                else
                {
                    sample[0] = table[(sampleByte & 0xF0) >> 4];
                    sample[1] = table[sampleByte & 0x0F];
                }

				// check for loop
				if (sample[0] == 8)
				{
					loopStart = result.Count;
					loopEnabled = true;
				}
				else if (sample[1] == 8)
				{
					loopStart = result.Count + 1;
					loopEnabled = true;
				}

                unchecked
                {
                    delta += sample[0];
                    delta &= 0x000000FF;
                    outputSample = delta << 24;
                    outputSample >>= 16;
                    result.Add((short)outputSample);
                    delta += sample[1];
                    delta &= 0x000000FF;
                    outputSample = delta << 24;
                    outputSample >>= 16;
                    result.Add((short)outputSample);
                }
            }

			Properties.LoopEnabled = loopEnabled;
			if (loopEnabled)
			{
				loopLength = result.Count - loopStart;
				Properties.LoopLength = loopLength;
				Properties.LoopStart = loopStart;
			}

            return result.ToArray();
        }

        private short[] Decode8Bit(Stream source)
        {
            List<short> result = new List<short>();
            int count = (int)source.Length;

            for (int i = 0; i < count; i++)
            {
                byte sampleByte = (byte)source.ReadByte();
                short outputSample = (short)sampleByte;
                outputSample <<= 8;
                result.Add(outputSample);
            }
            return result.ToArray();
        }

        private short[] Decode16Bit(Stream source)
        {
            List<short> result = new List<short>();
            int count = (int)source.Length / 2;
            byte[] sampleBytes = new byte[2];

            for (int i = 0; i < count; i++)
            {
                source.Read(sampleBytes, 0, 2);

                short outputSample = (short)sampleBytes[1];
                outputSample <<= 8;
                outputSample |= (short)sampleBytes[0];
                result.Add(outputSample);
            }
            return result.ToArray();
        }
    }
}
